package com.miniserver.tql.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Timer;

import org.apache.commons.lang.StringUtils;
import org.jfree.util.Log;

import com.miniserver.tql.TqlGlobal;
import com.miniserver.tql.TqlMaster;
import com.miniserver.tql.TqlPool;
import com.miniserver.tql.database.Configuration;
import com.miniserver.tql.database.ConnectionPool;
import com.miniserver.tql.database.PoolStatus;
import com.miniserver.tql.database.impl.ConnectionPoolImpl;
import com.miniserver.tql.task.HsqlSyncTask;
import com.miniserver.tql.task.SolrSyncTask;
import com.miniserver.util.FileUtils;
import com.miniserver.util.LogUtil;
import com.miniserver.util.SolrUtil;

/**
 * 基于Hsql的memdb管理类 <br/>
 */
public class HsqlMaster implements TqlMaster {
	private static LogUtil log = LogUtil.getLogUtil("HsqlMaster");
	private HashMap<String, Integer> lastReadMap = new HashMap<String, Integer>();
	private HashMap<String, Long> syncIds = new HashMap<String, Long>();
	private HashMap<String, List<TqlPool>> memdbPools = new HashMap<String, List<TqlPool>>();
	private HashMap<String, ConnectionPool> physicalPools = new HashMap<String, ConnectionPool>();
	/* 配置项 */
	private Configuration config = null;
	private boolean isDebug = true;

	@Override
	public ConnectionPool getPhysicalPool(String dbName) {
		if (this.physicalPools.containsKey(dbName)) {
			return this.physicalPools.get(dbName);
		} else {
			return null;
		}
	}

	/**
	 * 根据配置文件初始化系统
	 */
	@Override
	public void init(final Configuration config) {
		try {
			this.config = config;
			// 读取物理库配置文件
			String[] physicaldbList = config.getParamValue(TqlGlobal.CK_PHYSICAL_DATABASE).split(",");
			for (String physicaldb : physicaldbList) {
				ConnectionPool physicalPool = new ConnectionPoolImpl(physicaldb, this.config);
				this.physicalPools.put(physicaldb, physicalPool);
			}
			// 读取内存数据库配置文件
			String[] memdbList = config.getParamValue(TqlGlobal.CK_MOMERY_DATABASE).split(",");
			for (final String memdb : memdbList) {
				String sourcePath = config.getWebRoot() + config.getParamValue(memdb + ".basepath");
				String tempPath = config.getWebRoot() + config.getParamValue(memdb + ".temppath");
				String dbname = config.getParamValue(memdb + ".dbname");
				String connectType = config.getParamValue(memdb + ".connecttype");
				Integer poolCount = Integer.valueOf(config.getParamValue(memdb + ".poolcount"));
				if (poolCount < 1) {
					poolCount = 1;
				}
				//
				String sqlPath = config.getWebRoot() + config.getParamValue(memdb + ".sqlpath");
				if (!sqlPath.endsWith("bin")) {
					sqlPath = sqlPath.concat("bin");
				}
				// 根据配置文件创建memdb
				List<TqlPool> readPools = new ArrayList<TqlPool>();
				for (int i = 1; i < poolCount; i++) {
					String dbFolder = tempPath + "/db" + i;
					FileUtils.copy(sourcePath, dbFolder);

					// 默认file模式
					String dbPath = dbFolder;
					if(connectType.equals(HsqlPool.CONNECT_TYPE_RES)) {
						dbPath = config.getParamValue(memdb + ".temppath") + "/db" + i;
					}
					
					readPools.add(newPool(dbPath, dbname, sqlPath, connectType));
				}
				this.memdbPools.put(memdb, readPools);
				this.lastReadMap.put(memdb, 0);
				// 设置timer, 启动内存数据库定时同步
				new Thread(new Runnable() {
					@Override
					public void run() {
						// 内存数据库同步
						dataSync();
						long syncStart = 0, syncInterval = 0;
						String[] syncHMS = config.getParamValue(memdb + ".synctime").split(":");
						if (syncHMS.length == 3) {
							syncStart = getSyncStart(syncHMS);
							syncInterval = 86400; // 24hours
						} else {
							syncInterval = Long.valueOf(config.getParamValue(memdb + ".syncinterval"));
							syncStart = syncInterval * 1000;
						}
						//
						if (syncInterval > 0) {
							timer.schedule(new HsqlSyncTask(), syncStart, syncInterval * 1000);
						}
						// solor同步
						SolrUtil.getInstance().synchroData();
						syncHMS = config.getParamValue(memdb + ".solrtime").split(":");
						if (syncHMS.length == 3) {
							syncStart = getSyncStart(syncHMS);
							syncInterval = 86400; // 24hours
						} else {
							syncInterval = Long.valueOf(config.getParamValue(memdb + ".solrinterval"));
							syncStart = syncInterval * 1000;
						}
						//
						if (syncInterval > 0) {
							timer.schedule(new SolrSyncTask(), syncStart, syncInterval * 1000);
						}
					}
				}).start();
			}
		} catch (Exception e) {
			Log.error("init", e);
		}
	}

	private long getSyncStart(String[] syncHMS) {
		Calendar now = Calendar.getInstance();
		Calendar startTime = Calendar.getInstance();
		startTime.set(Calendar.HOUR_OF_DAY, now.get(Calendar.HOUR_OF_DAY));
		startTime.set(Calendar.MINUTE, now.get(Calendar.MINUTE));
		startTime.set(Calendar.SECOND, now.get(Calendar.SECOND));
		if (startTime.before(now)) {
			startTime.add(Calendar.DAY_OF_YEAR, 1);
		}
		return startTime.getTimeInMillis() - now.getTimeInMillis();
	}

	/**
	 * 根据注册一个新的pool
	 */
	private TqlPool newPool(String dbFolder, String dbname, String sqlFile, String connectType) {
		HsqlPool hsqlPool = new HsqlPool(dbFolder, dbname, connectType);
		hsqlPool.setSyncSqlFile(sqlFile);
		return hsqlPool;
	}

	// 查找活动链接数最少的TqlPool
	// 为保证每个实例都有机会运行, 从最后访问的TqlPool开始查找
	@Override
	public TqlPool getMemdbPool(String dbName) {
		synchronized (this) {
			List<TqlPool> readPools = this.memdbPools.get(dbName);
			if (readPools == null) {
				return null;
			} else if (readPools.size() == 1) {
				return readPools.get(0);
			}

			Integer lastReadIndex = (this.lastReadMap.get(dbName) == null) ? 0 : this.lastReadMap.get(dbName);
			TqlPool returnPool = readPools.get(lastReadIndex);
			PoolStatus dstStatus = returnPool.getStatus();
			for (int j = 0; j < readPools.size(); j++) {
				if (j == lastReadIndex) {
					continue;
				}
				//
				TqlPool pool = readPools.get(j);
				PoolStatus status = pool.getStatus();
				if (status.getCurActive() < dstStatus.getCurActive()) {
					returnPool = pool;
					dstStatus = returnPool.getStatus();
					this.lastReadMap.put(dbName, j);
					continue;
				}
			}
			//
			return returnPool;
		}
	}

	@Override
	public List<TqlPool> getMemdbPools(String dbName) {
		return this.memdbPools.get(dbName);
	}

	@Override
	public void destroy() throws Exception {
		timer.cancel();
		for (Iterator<Entry<String, List<TqlPool>>> it = this.memdbPools.entrySet().iterator(); it.hasNext();) {
			Entry<String, List<TqlPool>> entry = it.next();
			for (TqlPool pool : entry.getValue()) {
				pool.shutdown();
			}
		}
	}

	@Override
	public boolean dataSync() {
		String[] memdbList = config.getParamValue(TqlGlobal.CK_MOMERY_DATABASE).split(",");
		for (final String dbName : memdbList) {
			if (!syncOneMemdb(dbName)) {
				return false;
			}
		}
		return true;
	}

	// 物理库到内存库的同步Timer
	private static Timer timer = new Timer();

	private boolean syncOneMemdb(String memdbName) {
		boolean result = false;
		String sqlPath = config.getWebRoot() + config.getParamValue(memdbName + ".sqlpath");
		if (!sqlPath.endsWith("bin")) {
			sqlPath = sqlPath.concat("bin");
		}

		long currentSyncId = this.syncIds.containsKey(memdbName) ? this.syncIds.get(memdbName) : 0;
		String syncIdSQL = config.getParamValue(memdbName + ".syncidsql");
		String syncIdHolder = config.getParamValue(memdbName + ".syncidholder");
		long nextSyncId = this.initSyncId(memdbName, syncIdSQL);
		if (isDebug) {
			log.debug("初始化memdb数据任务开始。");
		}
		log.debug("开始初始化memdb");
		Long begin = System.currentTimeMillis();
		try {
			HsqlDataSync task = new HsqlDataSync(this, memdbName, sqlPath, syncIdHolder, currentSyncId);
			task.setDebug(isDebug);
			result = task.sync();
			if (result) {
				this.syncIds.put(memdbName, nextSyncId);
				if (isDebug) {
					log.debug("初始化memdb数据成功，用时" + (System.currentTimeMillis() - begin) + "毫秒。");
				}
				log.debug("初始化memdb数据成功");
			}
			return result;
		} catch (Exception e) {
			if (isDebug) {
				log.debug("初始化memdb数据失败！");
			}
			log.error("loadFromPhysical", e);
		}
		return false;
	}

	private long initSyncId(String memdbName, String syncIdSQL) {
		if (StringUtils.isBlank(syncIdSQL)) {
			return 0;
		}
		//
		Connection conn = null;
		PreparedStatement state = null;
		ResultSet rs = null;
		try {
			String[] ss = syncIdSQL.split("@");
			String physicaldbName, sql;
			if (ss.length > 1) {
				physicaldbName = ss[0];
				sql = ss[1];
			} else {
				physicaldbName = memdbName;
				sql = ss[0];
			}
			//
			ConnectionPool pool = this.getPhysicalPool(physicaldbName);
			conn = pool.getConnection();
			state = conn.prepareStatement(sql);
			rs = state.executeQuery();
			if (rs.next()) {
				return rs.getLong(1);
			} else {
				return 0;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		} finally {
			// close ResultSet
			if (rs != null) {
				try {
					rs.close();
				} catch (Exception e) {
				}
			}
			// close PreparedStatement
			if (state != null) {
				try {
					state.close();
				} catch (Exception e) {
				}
			}
			// close Connection
			if (conn != null) {
				try {
					conn.close();
				} catch (Exception e) {
				}
			}
		}// finally
	}
}